﻿using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Black.SurfaceToolkit.Controls
{
    /// <summary>
    /// Représente un <see cref="T:System.Windows.Controls.StackPanel" /> en arc de cercle.
    /// </summary>
    /// <remarks>
    /// <para>Ce contrôle dispose ses éléments enfants sur l'arc de cercle centré du contrôle.</para>
    /// <para>Le rayon de l'arc de cercle est définit par <see cref="P:Black.SurfaceToolkit.Controls.ArcStackPanel.Distance" />.</para>
    /// <para><div class="caption" style="font-weight : bold">ArcStackPanel à grande distance</div></para>
    /// <para><img src="../Images/ArcStackPanel.Big.png" /></para>
    /// <para><div class="caption" style="font-weight : bold">ArcStackPanel à petite distance</div></para>
    /// <para><img src="../Images/ArcStackPanel.Small.png" /></para>
    /// <para><see cref="P:System.Windows.Controls.StackPanel.Orientation" /> détermine la position du centre de l'arc de cercle par rapport au centre du contrôle.</para>
    /// <para><div class="caption" style="font-weight : bold">ArcStackPanel vertical</div></para>
    /// <para><img src="../Images/ArcStackPanel.Vertical.Small.png" /></para>
    /// </remarks>
    [Browsable(true), Description("Dispose les élements en arc de cercle")]
    public class ArcStackPanel : StackPanel
    {
        #region Propriétées de dépendances

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.ArcStackPanel.Distance" />.
        /// </summary>
        public static readonly DependencyProperty DistanceProperty = DependencyProperty.Register("Distance", typeof(double), typeof(ArcStackPanel), new FrameworkPropertyMetadata(100.0, FrameworkPropertyMetadataOptions.AffectsMeasure), new ValidateValueCallback(ArcStackPanel.IsValidDoubleValue));

        #endregion

        #region Accesseurs

        /// <summary>
        /// Obtient ou définit la distance au centre du panneau
        /// </summary>
        [Browsable(true), DefaultValue(100.0), Description("Rayon de l'arc de cercle"), Category("ArcStackPanel")]
        public double Distance
        {
            get
            {
                return ((double)this.GetValue(ArcStackPanel.DistanceProperty));
            }
            set
            {
                this.SetValue(ArcStackPanel.DistanceProperty, value);
            }
        }

        #endregion

        #region Constructeurs

        /// <summary>
        /// Initialise une nouvelle instance de <see cref="T:Black.SurfaceToolkit.Controls.ArcStackPanel" />.
        /// </summary>
        public ArcStackPanel()
            : base()
        {
        }

        #endregion

        #region Fonctions protégées

        /// <summary>
        /// Réarrange l'élément et ses enfants.
        /// </summary>
        /// <param name="p_oArrangeSize">Taille maximum autorisée.</param>
        /// <returns>Taille utilisée.</returns>
        protected override Size ArrangeOverride(Size p_oArrangeSize)
        {
            Size result = base.ArrangeOverride(p_oArrangeSize);

            UIElementCollection oChildren = this.InternalChildren;
            RotateTransform[] oRotations = new RotateTransform[oChildren.Count];

            Point oCenterPoint = new Point(p_oArrangeSize.Width / 2.0, p_oArrangeSize.Height / 2.0);

            double dAngle = 0.0;

            for (int i = 0; i < oChildren.Count; i++)
            {
                UIElement oChild = oChildren[i];

                double dSize = (Orientation == Orientation.Horizontal) ? oChild.DesiredSize.Width : oChild.DesiredSize.Height;

                double dCoveredAngle = (180.0 / Math.PI) * 2.0 * Math.Atan((dSize / 2.0) / Distance);

                oRotations[i] = new RotateTransform()
                {
                    CenterX = Orientation == Orientation.Vertical ? Distance : 0.0,
                    CenterY = Orientation == Orientation.Horizontal ? Distance : 0.0,
                    Angle = dAngle + dCoveredAngle / 2.0
                };

                dAngle += dCoveredAngle;

                if (this.Orientation == Orientation.Horizontal)
                {
                    if (this.Distance > 0.0)
                        oChild.RenderTransformOrigin = new Point(0.5, 1.0);
                    else
                        oChild.RenderTransformOrigin = new Point(0.5, 0);
                }
                else
                {
                    if (this.Distance > 0.0)
                        oChild.RenderTransformOrigin = new Point(1.0, 0.5);
                    else
                        oChild.RenderTransformOrigin = new Point(0, 0.5);
                }
            }

            double dHalfAngle = dAngle / 2.0;

            for (int i = 0; i < oChildren.Count; i++)
            {
                UIElement oChild = oChildren[i];
                Point oLocation = new Point(oCenterPoint.X - oChild.DesiredSize.Width / 2.0, oCenterPoint.Y - oChild.DesiredSize.Height / 2.0);

                oChild.Arrange(new Rect(oLocation, oChild.DesiredSize));

                RotateTransform oRotation = oRotations[i];
                oRotation.Angle -= dHalfAngle;
                oChild.RenderTransform = oRotation;
            }

            // On renvoi le résultat obtenu

            return (result);
        }

        /// <summary>
        /// Obtient la géométrie de découpe.
        /// </summary>
        /// <param name="p_oLayoutSlotSize">Taille de l'élément.</param>
        /// <returns>Géométrie de découpe.</returns>
        protected override Geometry GetLayoutClip(Size p_oLayoutSlotSize)
        {
            return (null);
        }

        #endregion

        #region Fonctions statiques

        /// <summary>
        /// Valide l'entier spécifié.
        /// </summary>
        /// <param name="p_oValue">Entier à valider.</param>
        /// <returns>Valeur indiquant si l'entier est valide</returns>
        public static bool IsValidDoubleValue(object p_oValue)
        {
            double dDoubleValue = (double)p_oValue;

            return (!Double.IsInfinity(dDoubleValue) && !Double.IsNaN(dDoubleValue));
        }

        #endregion
    }
}
